/*******************************************************************************
 * This file is part of Crunch Network.
 *
 * Crunch Network is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * Crunch Network is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with
 * Crunch Network.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/

package com.crunch.network;

/**
 * The {@code NetworkAddress} class encapsulates the address of a device on the network.
 *
 * TODO: ipv6 support
 */
public class NetworkAddress {
	/**
	 * Constructs a {@code NetworkAddress} with IP address 0.0.0.0 and port 0.
	 */
	public NetworkAddress() {
		address = 0x00000000;
		port = 0;
	}

	/**
	 * Constructs a {@code NetworkAddress} by copying the one provided.
	 *
	 * @param   other the address to copy.
	 */
	public NetworkAddress(NetworkAddress other) {
		address = other.address;
		port = other.port;
	}

	/**
	 * Constructs a {@code NetworkAddress} with the given IP address and port.
	 *
	 * @param   address the IP address.
	 * @param   port the port, which must be in the range 0 to 65535.
	 * @throws  IllegalArgumentException if the port is out of range.
	 */
	public NetworkAddress(int address, int port) {
		if (port < 0 || port > 65535) {
			throw new IllegalArgumentException("Port " + port + " out of range");
		}
		this.address = address;
		this.port = port;
	}

	/**
	 * Constructs a {@code NetworkAddress} with the given IP address and port.
	 *
	 * @param   a the first byte of the IP address.
	 * @param   b the second byte of the IP address.
	 * @param   c the third byte of the IP address.
	 * @param   d the forth byte of the IP address.
	 * @param   port the port, which must be in the range 0 to 65535.
	 * @throws  IllegalArgumentException if any of the bytes in the IP address are out of range, or if the port is out
	 *          of range.
	 */
	public NetworkAddress(int a, int b, int c, int d, int port) {
		if (a < 0 || a > 255) {
			throw new IllegalArgumentException("First IP address byte " + a + " out of range");
		}
		if (b < 0 || b > 255) {
			throw new IllegalArgumentException("Second IP address byte " + b + " out of range");
		}
		if (c < 0 || c > 255) {
			throw new IllegalArgumentException("Third IP address byte " + c + " out of range");
		}
		if (d < 0 || d > 255) {
			throw new IllegalArgumentException("Forth IP address byte " + d + " out of range");
		}
		if (port < 0 || port > 65535) {
			throw new IllegalArgumentException("Port " + port + " out of range");
		}

		address = (a << 24) | (b << 16) | (c << 8) | d;
		this.port = port;
	}

	/**
	 * Returns the IP address.
	 *
	 * @return  the IP address.
	 */
	public int getAddress() {
		return address;
	}

	/**
	 * Returns the {@code i}th byte in the IP address.
	 *
	 * @param   i the byte in the IP address to return.
	 * @return  the {@code i}th byte in the IP address.
	 * @throws  IllegalArgumentException if {@code i} is out of range.
	 */
	public int getAddressByte(int i) {
		if (i < 0 || i > 3) {
			throw new IllegalArgumentException("IP address byte " + i + " out of range");
		}
		return (address >> ((3-i)*8)) & 0xFF;
	}

	/**
	 * Returns the port.
	 *
	 * @return  the port.
	 */
	public int getPort() {
		return port;
	}

	/**
	 * Returns a hash code of the {@code NetworkAddress}.
	 *
	 * @return  A hash code of the {@code NetworkAddress}.
	 */
	@Override
	public int hashCode() {
		return address ^ port;
	}

	/**
	 * Returns whether this {@code NetworkAddress} is equal to {@code obj}. If {@code obj} is another
	 * {@code NetworkAddress} instance, the two are considered equal if both IP addresses and ports are equal.
	 *
	 * @param   obj the object to compare against.
	 * @return  whether this {@code NetworkAddress} is equal to {@code obj}.
	 */
	@Override
	public boolean equals(Object obj) {
		if (obj == null) {
			return false;
		}
		if (obj == this) {
			return true;
		}
		if (!(obj instanceof NetworkAddress)) {
			return false;
		}

		NetworkAddress other = (NetworkAddress) obj;
		return address == other.address && port == other.port;
	}

	/**
	 * Returns a formatted string of the IP address. The string is of the form byte0.byte1.byte2.byte3.
	 *
	 * @return  a formatted string of the IP address.
	 */
	public String getAddressString() {
		StringBuilder sb = new StringBuilder(15); // xxx.xxx.xxx.xxx is 15 characters
		sb.append((address >>> 24) & 0xFF);
		sb.append('.');
		sb.append((address >>> 16) & 0xFF);
		sb.append('.');
		sb.append((address >>> 8) & 0xFF);
		sb.append('.');
		sb.append(address & 0xFF);
		return sb.toString();
	}

	/**
	 * Returns a formatted string of the IP address and port. The string is of the form byte0.byte1.byte2.byte3:port.
	 *
	 * @return  a formatted string of the IP address and port.
	 */
	public String getAddressPortString() {
		StringBuilder sb = new StringBuilder(21); // xxx.xxx.xxx.xxx:yyyyy is 21 characters
		sb.append((address >>> 24) & 0xFF);
		sb.append('.');
		sb.append((address >>> 16) & 0xFF);
		sb.append('.');
		sb.append((address >>> 8) & 0xFF);
		sb.append('.');
		sb.append(address & 0xFF);
		sb.append(':');
		sb.append(port);
		return sb.toString();
	}

	private int address;    // the IP address
	private int port;       // the port
}
